// Copyright 2009 Google Inc. All Rights Reserved.

package spanishgringo.data;

import com.google.gdata.client.analytics.AnalyticsService;
import com.google.gdata.client.analytics.DataQuery;
import com.google.gdata.data.analytics.AccountEntry;
import com.google.gdata.data.analytics.AccountFeed;
import com.google.gdata.data.analytics.DataEntry;
import com.google.gdata.data.analytics.DataFeed;
import com.google.gdata.data.analytics.Segment;
import com.google.gdata.util.ServiceException;
import com.google.gdata.data.analytics.Metric;


import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.Locale;
import java.util.logging.Logger;

/**
 * Makes all requests to the Google Analytics Data Export API.
 * @author api.nickm@google.com (Nick Mihailovski)
 */
public class AnalyticsServiceWrapper {

  /** The URL all Google Analytics Data Feed requests start with */
  private static final String BASE_DATA_FEED_URL = "https://www.google.com/analytics/feeds/data";

  private AnalyticsService analyticsService;
  private AuthorizationService authService;
  private String tableId;
  private String profileName;
  private String startDate;
  private String endDate;
  private String segment;
  private String segmentName;
  private String queryURL;
  private Boolean tokenValid;
  private String accountListError;
  private String dataListError;
  private static final Logger log = Logger.getLogger("AnalyticsServiceWrapper");

  /**
   * Constructor.
   * @param analyticsService The AnalyticsService object to make requests to the
   *     Google Analytics API.
   * @param authService The AuthorizationService implementation used to add the authorization
   *     token into the Google Analytics service object.
   * @param tableId the ids parameter of the Data Feed query that corresponds to the Google
   *     Analytics tableId.
   */
  public AnalyticsServiceWrapper(AnalyticsService analyticsService,
      AuthorizationService authService, String tableId, String startDate, String endDate, String segment, String segmentName) {

    this.analyticsService = analyticsService;
    this.authService = authService;
    this.tableId = tableId;
    this.startDate = startDate;
    this.endDate = endDate;
    this.segment=segment;
    this.segmentName=segmentName;
    this.profileName = "";
    

    // Increase the request timeout to 10 seconds for App Engine.
    analyticsService.setConnectTimeout(10000);
  }

  /**
   * Constructor.
   * @param analyticsService The AnalyticsService object to make requests to the
   *     Google Analytics API.
   * @param authService The AuthorizationService implementation used to add the authorization
   *     token into the Google Analytics service object.
   * @param tableId the ids parameter of the Data Feed query that corresponds to the Google
   *     Analytics tableId.
   */
  public AnalyticsServiceWrapper(AnalyticsService analyticsService,
	      AuthorizationService authService, String tableId, String startDate, String endDate) {
	  new AnalyticsServiceWrapper(analyticsService, authService, tableId, startDate, endDate, null, "Unknown");
  }
  
  /**
   * Sets the token into the analytics service object. The logic to set the token is in the
   * AuhtorizationSerivice because each authorization implementation handles setting the
   * token differently.
   */
  public void setToken(UserToken userToken) {
    tokenValid = false;
    if (userToken.hasSessionToken()) {
      authService.putTokenInGoogleService(userToken, analyticsService);

      // Assume the token is valid.
      tokenValid = true;
    }
  }

  /**
   * Returns the table id.
   * @return the table id.
   */
  public String getTableId() {
    return tableId;
  }

  /**
   * Returns if the token was valid.
   * @return if the token was valid.
   */
  public Boolean isTokenValid() {
    return tokenValid;
  }

  /**
   * Returns a URL to query the Google Analytics API account feed. This query retrieves the 1000
   * 1000 accounts the current authorized user has access to.
   * @return the account feed URL.
   * @throws MalformedURLException if the URL isn't correct formed.
   */
  private URL getAccountFeedQuery() throws MalformedURLException {
    return new URL("https://www.google.com/analytics/feeds/accounts/default");
  }
  
  /**
   * Requests the Google Analytics account information profiles for the currently authorized user.
   * Then extract the the profile name and table id into a list.
   * @return a list of string arrays with the data from the API.
   */

  public List<String []> getAccountList() {

    if (!isTokenValid()){
      return null;
    }

    Boolean firstEntry = true;
    List<String []> accountList = new ArrayList<String[]>();

    try {
      // Make a request to the Account Feed.
      AccountFeed accountFeed = analyticsService.getFeed(getAccountFeedQuery(), AccountFeed.class);

      // Put the results in a list of String arrays.
      for (AccountEntry entry : accountFeed.getEntries()) {
        accountList.add(new String[] {
          entry.getTableId().getValue(),
          entry.getTitle().getPlainText()
        });

        // If no tableId has been configured, use the first tableId from the 
        // Account feed as a default.
        if (firstEntry && tableId == null) {
          firstEntry = false;
          tableId = entry.getTableId().getValue();
        }
      }
    } catch (ServiceException e) {
      // If the token in the data store has been revoked, a 401 error is thrown. To continue,
      // the user can just request another token from the authorization routine. So no error
      // message is sent to the user.
      if (e.getHttpErrorCodeOverride() == 401) {
        tokenValid = false;
      } else {
    	  String errMsg = e.getMessage();
          errMsg = errMsg.startsWith("Timeout")? errMsg +"<br/><strong><em>Timeouts can occur due to AppEngine's limit on query length (10 seconds). Please try again.</em></strong>":errMsg;
          accountListError = errMsg;
      }
    } catch (IOException e) {
      String errMsg = e.getMessage();
      errMsg = errMsg.startsWith("Timeout")? errMsg +"<br/><strong><em>Timeouts can occur due to AppEngine's limit on query length (10 seconds). Please try again.</em></strong>":errMsg;
      accountListError = errMsg;
    }
    return accountList;
  }

  
  /**
   * Requests the Google Analytics account information for the profile given
   * Then extract the the profile name and table id into a list.
   * @return a list of string arrays with the data from the API.
   */
  public List<String []> getAccountList(String gaID) {

    if (!isTokenValid()){
      return null;
    }

    Boolean firstEntry = true;
    List<String []> accountList = new ArrayList<String[]>();

    try {
      // Make a request to the Account Feed.
      AccountFeed accountFeed = analyticsService.getFeed(getAccountFeedQuery(), AccountFeed.class);

      // Put the results in a list of String arrays.
      for (AccountEntry entry : accountFeed.getEntries()) {
        if( entry.getTableId().getValue().toString().equals(gaID)) {
        	accountList.add(new String[] {
    		 entry.getTableId().getValue(),
    		 entry.getTitle().getPlainText()
    	  	});
        }

        // If no tableId has been configured, use the first tableId from the 
        // Account feed as a default.
        if (firstEntry && tableId == null) {
          firstEntry = false;
          tableId = entry.getTableId().getValue();
        }
      }
    } catch (ServiceException e) {
      // If the token in the data store has been revoked, a 401 error is thrown. To continue,
      // the user can just request another token from the authorization routine. So no error
      // message is sent to the user.
      if (e.getHttpErrorCodeOverride() == 401) {
        tokenValid = false;
      } else {
        accountListError = e.getMessage();
      }
    } catch (IOException e) {
      accountListError = e.getMessage();
    }
    return accountList;
  }
  
  /**
   * Requests the Google Analytics account information profiles for the currently authorized user.
   * Then extract the the profile name, table id, Account Name and Account Id into a a list of GaProfile objects.
   * @return a list of GaProfile objects with the data from the API.
   */
  
  public List<GaProfile> getAccountList(boolean full) {

	            if (!isTokenValid()){
	              return null;
	            }

	            Boolean firstEntry = true;
	            List<GaProfile> accountList = new ArrayList<GaProfile>();

	            try {
	              // Make a request to the Account Feed.
	              AccountFeed accountFeed = analyticsService.getFeed(getAccountFeedQuery(), AccountFeed.class);

	              // Put the results in a list of String arrays.
	              for (AccountEntry entry : accountFeed.getEntries()) {

	  	        	
	  	        	accountList.add(new GaProfile(
	  	        			entry.getTableId().getValue(),
	  	        			entry.getTitle().getPlainText(),
	  	        			entry.getProperty("ga:accountName"),
	  	        			entry.getProperty("ga:accountId")
	  	    	  			)
	  	        	);

	                // If no tableId has been configured, use the first tableId from the 
	                // Account feed as a default.
	                if (firstEntry && tableId == null) {
	                  firstEntry = false;
	                  tableId = entry.getTableId().getValue();
	                }
	              }
	            } catch (ServiceException e) {
	              // If the token in the data store has been revoked, a 401 error is thrown. To continue,
	              // the user can just request another token from the authorization routine. So no error
	              // message is sent to the user.
	              if (e.getHttpErrorCodeOverride() == 401) {
	                tokenValid = false;
	              } else {
	                accountListError = e.getMessage();
	              }
	            } catch (IOException e) {
	              accountListError = e.getMessage();
	            }
	            return accountList;
	  }
  
  

  /**
   * Requests the Google Analytics account information profiles for the currently authorized user.
   * Then extract the the profile name, table id, Account Name and Account Id into a a list of GaProfile objects.
   * @return a list of GaProfile objects with the data from the API.
   */
  
  public List<String []> getSegmentList() {

	            if (!isTokenValid()){
	              return null;
	            }

	            Boolean firstEntry = true;
	            List<String []> segmentList = new ArrayList<String []>();

	            try {
	              // Make a request to the Account Feed.
	              AccountFeed accountFeed = analyticsService.getFeed(getAccountFeedQuery(), AccountFeed.class);

	              // Put the results in a list of String arrays.
	              for (Segment curSegment : accountFeed.getSegments()) {
  	        	
	            	  segmentList.add(new String[] {
	            			  curSegment.getId(),
	            			  curSegment.getName(),(curSegment.hasDefinition() ?
	            			  curSegment.getDefinition().toString():""),
	            	        });

	              }
	            } catch (ServiceException e) {
	              // If the token in the data store has been revoked, a 401 error is thrown. To continue,
	              // the user can just request another token from the authorization routine. So no error
	              // message is sent to the user.
	              if (e.getHttpErrorCodeOverride() == 401) {
	                tokenValid = false;
	              } else {
	                accountListError = e.getMessage();
	              }
	            } catch (IOException e) {
	              accountListError = e.getMessage();
	            }
	            return segmentList;
	  }
  
  
  
  
  /**
   * Returns a URL to query the Google Analytics API Data Feed. This query retrieves the top 100
   * landing pages and their entrance and bounce metrics sorted by entrances for the last 14 days
   * starting yesterday.
   * @return a Google Analytics API query.
   * @throws MalformedURLException
   */
  private URL getDataFeedQuery() throws MalformedURLException {

    // Formatter for date.
	  SimpleDateFormat gaDate = new SimpleDateFormat("yyyy-MM-dd");
	  Calendar lastMonth = Calendar.getInstance();
	  
	  if(startDate==null) {
		  lastMonth.add(Calendar.MONTH, -1);
		  lastMonth.set(Calendar.DAY_OF_MONTH, 1);
		  startDate = gaDate.format(lastMonth.getTime());
		  lastMonth.set(Calendar.DAY_OF_MONTH, lastMonth.getActualMaximum(Calendar.DAY_OF_MONTH));
		  endDate = gaDate.format(lastMonth.getTime());
	  }
		  

    // Make a query.
    DataQuery query = new DataQuery(new URL(BASE_DATA_FEED_URL));
    query.setIds(tableId);
    query.setDimensions("ga:year,ga:month");
    query.setMetrics("ga:visits,ga:transactions,ga:itemQuantity,ga:uniquePageviews,ga:bounces,ga:entrances");
    query.setSort("ga:year,ga:month"); 
    query.setMaxResults(500);
    query.setStartDate(startDate);
    query.setEndDate(endDate);
    if (segment !=null) {
    	query.setSegment(segment);
    }
    
    log.info(query.getUrl().toString());
    String theURL = query.getUrl().toString();
    return query.getUrl();
  }

  /**
   * Requests the Google Analytics profile information for the table id set in this object. If no
   * table id has been set, the table id of the first account retrieved in the setAccountList
   * method is used. This method then extracts the the top 10 ga:source, ga:medium dimensions
   * along with the ga:visits, ga:bouces metrics and sets them in this object's dataList member.
   * @return a list of string arrays with the data from the API.
   */
  public List<String []> getDataList() {
    List<String []> dataList = new ArrayList<String[]>();
    Double bounceRate = 0.0;
    Double eComRate = 0.0;

    String myVisitsCI = "";
    if (!isTokenValid()) {
      return dataList;
    }

    try {
       //request account feed to get profile name
       String[] profileList = getAccountList(tableId).get(0);
    	setProfileName(profileList[1]);
    	setQueryURL(getDataFeedQuery().toString());
    	// Make a request to the Data Feed.
      DataFeed dataFeed = analyticsService.getFeed(getDataFeedQuery(), DataFeed.class);
      dataList.add(new String[] {"Year", "Month", "Visits", "Transactions", "Quantity", "Unique Pg.V", "Bounce Rate", "eCommerce Conv.", "CI"});
      
      
      NumberFormat f = NumberFormat.getInstance(Locale.US);
      if (f instanceof DecimalFormat) {
    	  ((DecimalFormat) f).applyPattern("###.#");
      }
      
      
      // Put the results in a list of String arrays.
      for (DataEntry entry : dataFeed.getEntries()) {
        // Calculate bounce rate.
    	  bounceRate = entry.doubleValueOf("ga:bounces") / entry.doubleValueOf("ga:entrances") * 100;
    	  eComRate = entry.doubleValueOf("ga:transactions") / entry.doubleValueOf("ga:visits") * 100;
    	  Metric myVisits = entry.getMetric("ga:visits");
    	  
    	  myVisitsCI =String.valueOf(f.format(myVisits.getConfidenceInterval())); 
    	  dataList.add(new String[] {
          entry.stringValueOf("ga:year"),
          entry.stringValueOf("ga:month"),
          entry.stringValueOf("ga:visits"),
          entry.stringValueOf("ga:transactions"),
          entry.stringValueOf("ga:itemQuantity"),
          entry.stringValueOf("ga:uniquePageviews"),
          Double.toString(bounceRate),
          Double.toString(eComRate),
          myVisitsCI
        });
      }
    } catch (ServiceException e) {
      dataListError = e.getMessage();
    } catch (IOException e) {
      String errMsg = e.getMessage();
      errMsg = errMsg.startsWith("Timeout")? "<strong>"+errMsg.substring(0, errMsg.indexOf("http")) + "</strong>"+ errMsg.substring(errMsg.indexOf("http"))+"<br/><strong><em>This is common for long or complex queries due to AppEngine's limit on query length (10 seconds). GA caches results so please wait a few seconds &amp; try again.</em></strong>":errMsg;
      dataListError = errMsg;
    }
   catch (Exception e) {
      dataListError = e.getMessage();
    }
    return dataList;
  }

  /**
   * Returns the message from any errors encountered by retrieving account list.
   * @return the error encountered by retrieving the account list.
   */
  public String getAccountListError() {
    return accountListError;
  }

  /**
   * Returns the message from any errors encountered by retrieving the data list.
   * @return the error encountered by retrieving the data list.
   */
  public String getDataListError() {
    return dataListError;
  }

/**
 * @param startDate the startDate to set
 */
public void setStartDate(String startDate) {
	this.startDate = startDate;
}

/**
 * @return the startDate
 */
public String getStartDate() {
	return startDate;
}

/**
 * @param endDate the endDate to set
 */
public void setEndDate(String endDate) {
	this.endDate = endDate;
}

/**
 * @return the endDate
 */
public String getEndDate() {
	return endDate;
}

public void setSegment(String segment) {
	this.segment = segment;
}

/**
 * @return the endDate
 */
public String getSegment() {
	return segment;
}

/**
 * @param profileName the profileName to set
 */
public void setProfileName(String profileName) {
	this.profileName = profileName;
}

/**
 * @return the profileName
 */
public String getProfileName() {
	return profileName;
}

/**
 * @param queryURL the queryURL to set
 */
public void setQueryURL(String queryURL) {
	this.queryURL = queryURL;
}

/**
 * @return the queryURL
 */
public String getQueryURL() {
	return queryURL;
}

/**
 * @param segmentName the segmentName to set
 */
public void setSegmentName(String segmentName) {
	this.segmentName = segmentName;
}

/**
 * @return the segmentName
 */
public String getSegmentName() {
	return segmentName;
}
}
